package com.kedacom.ctsp.authz.oauth2.provider;

import com.kedacom.ctsp.authz.AuthenticationService;
import com.kedacom.ctsp.authz.Authorize;
import com.kedacom.ctsp.authz.entity.AuthResource;
import com.kedacom.ctsp.authz.entity.Authentication;
import com.kedacom.ctsp.authz.entity.AuthzTypeEnum;
import com.kedacom.ctsp.authz.exception.NoPermissionAccessException;
import com.kedacom.ctsp.authz.exception.UnauthorizedException;
import com.kedacom.ctsp.authz.oauth2.core.AuthzConstants;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/***
 * 统一权限Url拦截，权限判断
 * @Author fenghaiju
 * @Date 2018-07-13
 */
@Slf4j
public class AuthzInterceptorHandler extends HandlerInterceptorAdapter {
    @Autowired
    protected AuthenticationService authenticationService;


    // 在业务处理器处理请求之前被调用
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String _method = request.getMethod();
        if (handler instanceof HandlerMethod) {
            Method method = ((HandlerMethod) handler).getMethod();

            Authorize classAnno = method.getDeclaringClass().getAnnotation(Authorize.class);
            Authorize methodAnno = method.getAnnotation(Authorize.class);
            Authorize anno = method.getAnnotation(Authorize.class) == null ? classAnno : methodAnno;
            //如果类上或者方法上有Authorize注解，则优先使用@Authorize注解的方式
            if (classAnno != null || methodAnno != null) {
                return true;
            } else {
                //使用url判断的方式，根据当前登录人来判断
                Authentication authentication = Authentication.current().orElseThrow(UnauthorizedException::new);
                Map map = getPathByAnnotation(method);
                Set<String> uri = new HashSet<>();
                uri.add(map.get("url").toString());
                List<AuthResource> authResources = authenticationService.loadResources(authentication, uri, AuthzTypeEnum.URI);
                if (CollectionUtils.isEmpty(authResources)) {
                    throw new NoPermissionAccessException();
                }
                authResources = authResources.stream().filter(r -> r.getMethod() != null && r.getMethod().name().contains(_method)).collect(Collectors.toList());
                request.setAttribute(AuthzConstants.AUTHZ_URL_CONTEXT, AuthzTypeEnum.URI);
                request.setAttribute(AuthzConstants.AUTH_RESOURCE_CONTEXT, authResources);
            }

        }
        return super.preHandle(request, response, handler);

    }

    // 在业务处理器处理请求完成之后，生成视图之前执行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

        super.postHandle(request, response, handler, modelAndView);
    }

    // 在DispatcherServlet完全处理完请求之后被调用，可用于清理资源
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        super.afterCompletion(request, response, handler, ex);
    }

    @Override
    public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        super.afterConcurrentHandlingStarted(request, response, handler);
    }

    /**
     * 异常处理
     *
     * @param request
     * @param response
     * @param handler
     * @throws ServletException
     * @throws IOException
     */
    protected void handleNotAuthorized(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws ServletException, IOException {
        // 403表示资源不可用。服务器理解用户的请求，但是拒绝处理它，通常是由于权限的问题
        response.sendError(403);
    }

    /**
     * 通过方法获取请求地址
     *
     * @param method
     * @return
     */
    private Map getPathByAnnotation(Method method) {
        String parentPath = "";
        String childPath = "";
        String _method = "";
        if (method.getDeclaringClass().isAnnotationPresent(RequestMapping.class)) {
            RequestMapping requestMapping = method.getDeclaringClass().getAnnotation(RequestMapping.class);
            parentPath = requestMapping.value().length == 0 ? "" : requestMapping.value()[0];
            if (StringUtils.isNotBlank(parentPath) && !parentPath.substring(0, 1).contains("/")) {
                parentPath = "/" + parentPath;
            }
        }
        if (method.isAnnotationPresent(RequestMapping.class)) {
            RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
            childPath = requestMapping.value().length == 0 ? "" : requestMapping.value()[0];
            _method = requestMapping.method().length == 0 ? RequestMethod.GET.name() : requestMapping.method()[0].name();
        } else if (method.isAnnotationPresent(GetMapping.class)) {
            GetMapping requestMapping = method.getAnnotation(GetMapping.class);
            childPath = requestMapping.value().length == 0 ? "" : requestMapping.value()[0];
            if (StringUtils.isBlank(childPath)) {
                childPath = requestMapping.path().length == 0 ? "" : requestMapping.path()[0];
            }
            _method = RequestMethod.GET.name();
        } else if (method.isAnnotationPresent(PutMapping.class)) {
            PutMapping requestMapping = method.getAnnotation(PutMapping.class);
            childPath = requestMapping.value().length == 0 ? "" : requestMapping.value()[0];
            if (StringUtils.isBlank(childPath)) {
                childPath = requestMapping.path().length == 0 ? "" : requestMapping.path()[0];
            }
            _method = RequestMethod.PUT.name();
        } else if (method.isAnnotationPresent(DeleteMapping.class)) {
            DeleteMapping requestMapping = method.getAnnotation(DeleteMapping.class);
            childPath = requestMapping.value().length == 0 ? "" : requestMapping.value()[0];
            if (StringUtils.isBlank(childPath)) {
                childPath = requestMapping.path().length == 0 ? "" : requestMapping.path()[0];
            }
            _method = RequestMethod.DELETE.name();
        } else if (method.isAnnotationPresent(PostMapping.class)) {
            PostMapping requestMapping = method.getAnnotation(PostMapping.class);
            childPath = requestMapping.value().length == 0 ? "" : requestMapping.value()[0];
            if (StringUtils.isBlank(childPath)) {
                childPath = requestMapping.path().length == 0 ? "" : requestMapping.path()[0];
            }
            _method = RequestMethod.POST.name();
        } else if (method.isAnnotationPresent(PatchMapping.class)) {
            PatchMapping requestMapping = method.getAnnotation(PatchMapping.class);
            childPath = requestMapping.value().length == 0 ? "" : requestMapping.value()[0];
            if (StringUtils.isBlank(childPath)) {
                childPath = requestMapping.path().length == 0 ? "" : requestMapping.path()[0];
            }
            _method = RequestMethod.PATCH.name();

        }
        if (StringUtils.isNotBlank(childPath)) {
            childPath = "/" + childPath;
        }
        Map map = new HashMap();
        map.put("url", parentPath + childPath);
        map.put("method", _method);
        return map;

    }


}
